package com.eastedu.service;

import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.eastedu.common.thread.TaskExecutor;
import com.eastedu.exception.ServiceException;
import com.eastedu.model.RequestParameter;
import com.eastedu.repository.permission.po.BaseEntity;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 〈流程控制〉
 *
 * @param <T> the type parameter
 * @author ZHISHUAI.ZHANG
 * @since 2021 /7/28
 */
@Slf4j
public abstract class AbstractDataSyncService<T extends BaseEntity> implements DataSourceService {

    private final BaseMapper<T> mapper;
    /**
     * -- GETTER --
     * Gets clazz.
     */
    @Getter
    private final Class<T> clazz;
    private final JdbcTemplate jdbcTemplate;
    private final MultiValueMap<String, String> sqlScripts = new LinkedMultiValueMap<>();
    private final Semaphore semaphore = new Semaphore(2);


    /**
     * Instantiates a new Abstract data sync service.
     *
     * @param mapper the mapper
     */
    protected AbstractDataSyncService(BaseMapper<T> mapper) {
        this.mapper = mapper;
        this.clazz = getParameterizedClass();
        jdbcTemplate = SpringUtil.getBean(JdbcTemplate.class);
    }

    @Async
    @Override
    public void sync(RequestParameter parameter) {
        try {
            LambdaQueryWrapper<T> wrapper = getExample();
            int limit = 100;
            int total = Math.toIntExact(this.mapper.selectCount(wrapper));
            if (total < 1) {
                log.info("数据源[{}]无数据， 同步任务已完成", clazz.getSimpleName());
                return;
            }
            int totalPage = (total + limit - 1) / limit;
            AtomicLong count = new AtomicLong(totalPage);
            CountDownLatch countDown = new CountDownLatch(totalPage);
            for (int i = 0; i < totalPage; i++) {
                int page = i;
                semaphore.acquire();
                TaskExecutor.execute(() -> {
                    int offset = page * limit;
                    try {
                        Page<T> pageResult = this.pageList(Page.of(offset, limit, false), wrapper);
                        doSync(pageResult.getRecords());
                        log.info("<------ 数据源[{}] 剩余页码:{} ------>", clazz.getSimpleName(), count.decrementAndGet());
                    } finally {
                        countDown.countDown();
                        semaphore.release();
                    }
                }, parameter.getAsync());
            }
            countDown.await();
            this.sync(parameter);
        } catch (InterruptedException e) {
            throw new ServiceException(e, "同步数据失败");
        }
    }

    @Async
    @Override
    public void runScript(String stage) {
        final List<String> scripts = this.sqlScripts.get(stage);
        scripts.forEach(script -> {
            if (StringUtils.isBlank(script)) {
                throw new ServiceException("无效的SQL");
            }
            this.jdbcTemplate.execute(script);
            log.info("Complete executing SQL statement [ {} ]", script);
        });
    }

    /**
     * 更新实体内容
     *
     * @param source 实体
     */
    protected abstract void updateContent(T source);

    /**
     * Add script by table name.
     *
     * @param tableName the table name
     */
    protected void addScriptByTableName(String tableName) {
        this.addInitScriptByTableName(tableName);
        this.addPostScriptByTableName(tableName);
    }

    private void addInitScriptByTableName(String tableName) {
        String script = "ALTER TABLE " +
                tableName +
                " ADD COLUMN `sync_state` TINYINT(1) DEFAULT 0 COMMENT '临时字段(同步数据用，用完删除)';";
        this.sqlScripts.add("init", script);
    }

    private void addPostScriptByTableName(String tableName) {
        String script = "ALTER TABLE " +
                tableName +
                " DROP COLUMN `sync_state`;";
        this.sqlScripts.add("post", script);
    }

    private String getJdbcBeanName() {
        String tempName = this.getClass().getSimpleName().replaceAll("[A-Z]", "_$0");
        return tempName.split("_")[1].toLowerCase() + "JdbcTemplate";
    }

    private Page<T> pageList(Page<T> page, LambdaQueryWrapper<T> example) {
        return this.mapper.selectPage(page, example);
    }

    private void doSync(List<T> sources) {
        sources.forEach(source -> {
            this.updateContent(source);
            source.setSyncState(Boolean.TRUE);
            this.mapper.updateById(source);
        });
    }

    /**
     * Gets example.
     *
     * @return the example
     */
    public LambdaQueryWrapper<T> getExample() {
        return Wrappers.lambdaQuery(clazz)
                .eq(BaseEntity::getSyncState, 0)
                .orderByAsc(BaseEntity::getId);
    }

    private Class<T> getParameterizedClass() {
        Type genType = getClass().getGenericSuperclass();
        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
        final Type type = params[0];
        if (type instanceof Class) {
            return (Class<T>) type;
        }
        throw new ServiceException("泛型参数类型获取失败");
    }

    /**
     * Add script.
     *
     * @param sql the sql
     */
    protected void addScript(String sql) {
        this.sqlScripts.add("init", sql);
    }

}